﻿using System;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Diagnostics;
using System.Reflection;

namespace AppFuscator
{
    public partial class MainForm : Form
    {
        private ApiRequest uploader = ApiRequest.Instance;
        private VisualProject project;
        private AuthForm authorizer = new AuthForm();
        private bool SettingsWriting;
        private AppfuscatorResult result;
        private bool threadUpload;
        private bool threadInitializing;
        private bool authorized = false;

        public MainForm()
            : this(new String[]{})
        {
        }

        public MainForm(String[] args)
        {
            InitializeComponent();
            project = new VisualProject(ctrlListView);
            ctrlListView.SelectedIndexChanged += ctrlListView_SelectedIndexChanged;

            if (args.Length > 0)
                AddFilesToList(args);

            threadInitializing = true;
            UpdateButtons();
            ThreadPool.QueueUserWorkItem((a) => Authorize());
            ctrlListView.Focus();

            foreach (var it in RenameSchemeConverter.AllNames)
                cmbMethodName.Items.Add(it);
        }

        private void Authorize()
        {
            var init = uploader.Initialize();
            if (!init.HasValue)
            {
                System.Windows.Forms.MessageBox.Show("Temporary error, please try again.");
                Environment.Exit(0);
            }

            if (init.HasValue && !init.Value)
            {
                MessageBox.Show("Protocol has been changed. You should update program: http://" + Utils.MainHost);
                Environment.Exit(0);
            }
            else
            {

                authorized = authorizer.AutoAuth();
                this.Invoke(new Action(() =>
                {
                    SetAuthedInfo();
                }));
            }

            this.Invoke(new Action(() =>
            {
                threadInitializing = false;
                UpdateButtons();
            }));
        }

        private void SetAuthedInfo()
        {
            accountInfo.Text = User.Instance.Name;

            tsmiBalance.Visible = true;
            tsmiPaidPeriod.Visible = true;
            tsmiOpfuscationHistory.Visible = true;
            tsmiLogout.Visible = true;
            tsmiRegister.Visible = false;
            lblUnregistered.Visible = false;
            tsmiSignIn.Visible = false;

            lblImportant.Text = "";

            if (User.Instance.Mode == AccountMode.Demo)
            {
                tsmiPaidPeriod.Visible = false;
                tsmiBalance.Visible = false;
                tsmiOpfuscationHistory.Visible = false;
                tsmiLogout.Visible = false;
                tsmiRegister.Visible = true;
                tsmiSignIn.Visible = true;
                lblUnregistered.Visible = true;

                lblImportant.Text = "";
            }
            else if (User.Instance.Mode == AccountMode.Trial)
            {
                tsmiPaidPeriod.Text = "Trial period: " + User.Instance.Tariff.Remaning.ToString() + " " + Utils.CntCase(User.Instance.Tariff.Remaning, "day", "days");
                lblImportant.Text = string.Format("{0} {1}", User.Instance.Tariff.Remaning.ToString(), Utils.CntCase(User.Instance.Tariff.Remaning, "day", "days"));
                if (User.Instance.Tariff.Remaning > 0)
                    lblImportant.ToolTipText = string.Format("Trial period ends in {0} {1}", User.Instance.Tariff.Remaning.ToString(), Utils.CntCase(User.Instance.Tariff.Remaning, "day", "days"));
                else
                    lblImportant.ToolTipText = "Trial period will end tomorrow";
            }
            else
            {
                tsmiPaidPeriod.Text = "Payed period: " + User.Instance.Tariff.Remaning.ToString() + " " + Utils.CntCase(User.Instance.Tariff.Remaning, "day", "days");
                if (User.Instance.IsLimited)
                {
                    lblImportant.Text = "Limited";
                    lblImportant.ToolTipText = "Features is limited. Please refill your account to remove limits";
                }
                else if (User.Instance.Tariff.Remaning > 0 && User.Instance.Tariff.Remaning < 14)
                {
                    lblImportant.Text = string.Format("{0} {1}", User.Instance.Tariff.Remaning.ToString(), Utils.CntCase(User.Instance.Tariff.Remaning, "day", "days"));
                    lblImportant.ToolTipText = string.Format("You balance: {0} {1} \r\nPaid period: {2} {3}", User.Instance.Balance.ToString(), User.Instance.Currency, User.Instance.Tariff.Remaning.ToString(), Utils.CntCase(User.Instance.Tariff.Remaning, "day", "days"));
                }
                else if(User.Instance.Tariff.Remaning == 0)
                {
                    lblImportant.Text = string.Format("{0} {1}", User.Instance.Tariff.Remaning.ToString(), Utils.CntCase(User.Instance.Tariff.Remaning, "day", "days"));
                    lblImportant.ToolTipText = string.Format("You balance: {0} {1} \r\nTomorrow you will not be able to use all features until you refill your account", User.Instance.Balance.ToString(), User.Instance.Currency);
                }
                
            }

            if (User.Instance.Tariff.Remaning < 1)
                accountInfo.Image = Properties.Resources.account_red;
            else if (User.Instance.Tariff.Remaning > 0 && User.Instance.Tariff.Remaning < 14)
                accountInfo.Image = Properties.Resources.account_demo;
            else
                accountInfo.Image = Properties.Resources.account;

            SettingsWriting = true;
            var cfg = project.GetItem(ctrlListView.SelectedItem);
            if (cfg != null)
                ShowSetting(cfg);
            SettingsWriting = false;
            UpdateButtons();

            tsmiBalance.Text = "Balance: " + User.Instance.Balance.ToString() + " " + User.Instance.Currency;
        }

        private void InitCheckBoxes()
        {
            chkUnion.Enabled = UserTariff.Instance.AssemblyMerge;
            chkEncrypt.Enabled = UserTariff.Instance.EncodeStrings;
            chkMethodHide.Enabled = UserTariff.Instance.ExternalMethodCallHidding;
            chkDecomposition.Enabled = UserTariff.Instance.Decomposition;
            chkLibraryMode.Enabled = UserTariff.Instance.LibraryMode;
            chkControlFlow.Enabled = UserTariff.Instance.ControlFlow;
        }

        private void UpdateButtons()
        {
            var sel = ctrlListView.SelectedItem != null;

            if (threadUpload || threadInitializing)
            {
                btnAdd.Enabled = false;
                btnDel.Enabled = false;
                pnlConfig.Enabled = false;
                btnStart.Enabled = false;
                btnNew.Enabled = false;
                btnOpen.Enabled = false;
                btnSave.Enabled = false;
                btnDel.Enabled = false;
                picProcessing.Visible = true;
                lblWait.Visible = true;
                btnOpenFolder.Enabled = false;
                accountInfo.Enabled = false;
            }
            else
            {
                btnAdd.Enabled = true;
                pnlConfig.Enabled = sel;
                btnDel.Enabled = sel;
                btnStart.Enabled = ctrlListView.Count > 0;
                btnNew.Enabled = true;
                btnOpen.Enabled = true;
                btnSave.Enabled = ctrlListView.Count > 0;
                picProcessing.Visible = false;
                lblWait.Visible = false;
                btnOpenFolder.Enabled = !string.IsNullOrEmpty(project.FullOutputPath) && Directory.Exists(project.FullOutputPath);
                accountInfo.Enabled = true;
            }
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            var x = Utils.SerializeObject(project as Project);
            var y = Utils.DeserializeObject<VisualProject>(x);

            if (threadUpload) return;
            threadUpload = true;
            UpdateButtons();
            tabPages.SelectedTab = tabProcessing;
            new Thread(StartProcessing) { IsBackground = true }.Start();
        }

        private void StartProcessing()
        {
            uploader.IsUploading = true;
            try
            {
                if (project.Count > 0)
                {
                    this.Invoke(new Action(() => resultDisplay.Clear()));

                    if (!uploader.Begin())
                    {
                        throw new UploadException("Server unavailable");
                    }

                    List<string> errors = new List<string>();
                    foreach (var path in project.Items)
                    {
                        if (!File.Exists(path.FileName))
                        {
                            errors.Add(string.Format(UploadStatusConverter.ToString(UploadStatus.Removed), path.Title));
                            continue;
                        }

                        int code = uploader.UploadFile(path.FileName, false);
                        if (code != 0)
                        {
                            errors.Add(string.Format(UploadStatusConverter.ToString(code), path.Title));
                        }
                    }

                    if (errors.Count > 0)
                        throw new UploadException(string.Join("`", errors));

                    int obfuscationId = uploader.SetField(project.GetAssembliesSettings());
                    result = uploader.GetResult(obfuscationId);
                    if (result.State == State.Success)
                        uploader.ExtractZip(result, project.FullOutputPath);
                }
            }
            catch (UploadException e)
            {
                AppfuscatorResult tempRes = new AppfuscatorResult();
                string[] messages = e.Message.ToString().Split('`');
                foreach (string message in messages)
                {
                    tempRes.AddToLog("error", message);
                }
                tempRes.State = State.Error;
                result = tempRes;

                this.Invoke(new Action(() => resultDisplay.Update(result)));
            }
            catch (Exception e)
            {
                MessageBox.Show("Exception: " + e.ToString());
            }
            finally
            {
                threadUpload = false;
                Invoke(new Action(() => FinishProcessing()));
            }

        }

        private void FinishProcessing()
        {
            UpdateButtons();
            btnStart.Text = "Start";
            resultDisplay.Update(result);
            uploader.IsUploading = false;
        }

        private void SettingForm_DragDrop(object sender, DragEventArgs e)
        {
            string[] newPaths = (string[])e.Data.GetData(DataFormats.FileDrop, false);
            AddFilesToList(newPaths);
        }

        private void AddFilesToList(params string[] newPaths)
        {
            Array.Sort(newPaths, Utils.CompareByExtension);
            Array.Reverse(newPaths);

            foreach (var file in newPaths)
            {
                var ext = Path.GetExtension(file);
                if (ext == ".afproj")
                {
                    var p = VisualProject.Load(file, ctrlListView);
                    if (p != null)
                    {
                        project = p;
                        UpdateButtons();
                        return;
                    }
                }

                if (file.Contains(".vshost.")) continue;
                if (project.ContainsFile(file)) continue;
                if (ext != ".exe" && ext != ".dll") continue;

                project.Add(new AssemblyItem(file));
            }
            UpdateButtons();
        }

        private void SettingForm_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                e.Effect = DragDropEffects.All;
            else
                e.Effect = DragDropEffects.None;
        }

        private void ctrlListView_SelectedIndexChanged(object sender, TileListItem item)
        {
            SettingsWriting = true;
            var cfg = project.GetItem(item);
            ShowSetting(cfg);
            SettingsWriting = false;
            this.tabPages.SelectedTab = this.tabConfig;
            UpdateButtons();
        }

        private void btnApplyToAll_Click(object sender, EventArgs e)
        {
            foreach (var it in project.Items)
                SaveSetting(it);
        }

        private void SettingsChanged(object sender, EventArgs e)
        {
            if (!SettingsWriting)
                SaveSetting(project.SelectedItem);
        }

        private void btnSetMain_Click(object sender, EventArgs e)
        {
            project.SelectedItem.MergeAssemble = false;
            project.MainAssemble = project.SelectedItem;
            ShowSetting(project.SelectedItem);
        }

        private void ShowSetting(AssemblyItem item)
        {
            InitCheckBoxes();

            lblAssemblyName.Text = item.Title;

            cmbMethodName.Text = RenameSchemeConverter.Convert(item.Scheme);
            chkEncrypt.Checked = item.EncryptString;
            chkMethodHide.Checked = item.HideExternalMethods;
            chkDecomposition.Checked = item.ClassDecomposition;
            chkUnion.Visible = project.MainAssemble != item;
            chkUnion.Checked = item.MergeAssemble;
            chkLibraryMode.Checked = item.LibraryMode;
            chkControlFlow.Checked = item.ControlFlow;
        }

        private void SaveSetting(AssemblyItem item)
        {
            item.Scheme = RenameSchemeConverter.Convert(cmbMethodName.Text);
            item.EncryptString = chkEncrypt.Checked;
            item.HideExternalMethods = chkMethodHide.Checked;
            item.ClassDecomposition = chkDecomposition.Checked;
            item.MergeAssemble = chkUnion.Checked;
            item.LibraryMode = chkLibraryMode.Checked;
            item.ControlFlow = chkControlFlow.Checked;

            project.RefreshItem(item);
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            OpenFileDialog op = new OpenFileDialog();
            op.Title = "Select file for upload";
            op.Filter = "All Supported Files|*.exe;*.dll|exe (*.exe)|*.exe|dll (*.dll)|*.dll|All files (*.*)|*.*";
            op.Multiselect = true;
            if (op.ShowDialog() == DialogResult.OK)
            {
                AddFilesToList(op.FileNames);
            }

            UpdateButtons();
        }

        private void btnDel_Click(object sender, EventArgs e)
        {
            project.Delete(project.SelectedItem);
            UpdateButtons();
        }

        private void btnNew_Click(object sender, EventArgs e)
        {
            ctrlListView.Clear();
            project = new VisualProject(ctrlListView);
            UpdateButtons();
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            (sender as ToolStripButton).Enabled = false;
            var p = VisualProject.Load(ctrlListView);
            if (p != null)
            {
                UpdateButtons();
                project = p;
            }
            (sender as ToolStripButton).Enabled = true;
            UpdateButtons();
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            project.Save();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (ApiRequest.Instance.IsUploading)
            {
                e.Cancel = (MessageBox.Show("You can't close program while files is uploading. Close anyway?", "Confirm", MessageBoxButtons.YesNo) == DialogResult.No);
            }
        }

        private void btnOpenFolder_Click(object sender, EventArgs e)
        {
            if (!Directory.Exists(project.FullOutputPath))
                Directory.CreateDirectory(project.FullOutputPath);
            Process.Start(project.FullOutputPath);
        }

        private void btnHelp_Click(object sender, EventArgs e)
        {
            Process.Start("http://" + Utils.MainHost + "/faq");
        }

        private void toolStripSplitButton1_ButtonClick(object sender, EventArgs e)
        {
            if (authorized)
            {
                string key = uploader.GenerateKey();

                if (key == null)
                    return;

                Process.Start("http://" + Utils.CabinetHost + "/?key=" + key);
            }
            else
            {
                SignIn();
            }
        }

        private void SignIn()
        {
            authorized = authorizer.ShowDialog() == DialogResult.OK;
            if (authorized)
                SetAuthedInfo();
        }

        private void miOpfuscationHistory_Click(object sender, EventArgs e)
        {
            string key = uploader.GenerateKey();

            if (key == null)
                return;

            Process.Start("http://" + Utils.CabinetHost + "/obfuscation/history?key=" + key);
        }

        private void tsmiLogout_Click(object sender, EventArgs e)
        {
            uploader.Logout();
            AppfuscatorConfig.Instance.RemoveLoginData();
            threadInitializing = true;

            ctrlListView.Focus();
            ThreadPool.QueueUserWorkItem((a) => Authorize());
            UpdateButtons();
        }

        private void tsmiRegister_Click(object sender, EventArgs e)
        {
            Process.Start("http://" + Utils.CabinetHost + "/register");
        }

        private void toolStripButton1_Click(object sender, EventArgs e)
        {
            if (!File.Exists(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\StackTraceDecoder.exe"))
            {
                MessageBox.Show("Module not found");
                return;
            }
            Process.Start(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\StackTraceDecoder.exe");
        }

        private void tsmiSignIn_Click(object sender, EventArgs e)
        {
            SignIn();
        }
    }
}